/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_texture.cpp - Image loading and texture creation
//


#include "r_local.h"


struct cubeSide_t
{
	char		*suffix;
	char		*separator;
	bool		flipX;
	bool		flipY;
	bool		flipDiagonal;
};

static cubeSide_t	r_cubeSides[2][6] = {
	{
		{"px"	, "_"	, false	, false	, false	},
		{"nx"	, "_"	, false	, false	, false	},
		{"py"	, "_"	, false	, false	, false	},
		{"ny"	, "_"	, false	, false	, false	},
		{"pz"	, "_"	, false	, false	, false	},
		{"nz"	, "_"	, false	, false	, false	}
	},
	{
		{"rt"	, ""	, false	, false	, true	},
		{"lf"	, ""	, true	, true	, true	},
		{"bk"	, ""	, false	, true	, false	},
		{"ft"	, ""	, true	, false	, false	},
		{"up"	, ""	, false	, false	, true	},
		{"dn"	, ""	, false	, false	, true	}
	}
};

static vec3_t		r_luminanceTable[256];
static uint		    r_palette[256];

static texture_t	*r_texturesHashTable[TEXTURES_HASH_SIZE];
static texture_t	*r_textures[MAX_TEXTURES];
static int			r_numTextures;


/*
 ==============================================================================

    PCX FORMAT LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadPCX
 ==================
*/
static bool R_LoadPCX (const char *name, byte **image, byte **palette, int *width, int *height, int *samples){

	byte		*buffer;
	byte		*in, *out;
	pcxHeader_t	*pcx;
	int			x, y, len;
	int			dataByte, runLength;
	bool	hasColor = false, hasAlpha = false;

	// Load the file
	len = FS_ReadFile(name, (void **)&buffer);
	if (!buffer)
		return false;

	// Parse the PCX file
	pcx = (pcxHeader_t *)buffer;

    pcx->xMin = LittleShort(pcx->xMin);
    pcx->yMin = LittleShort(pcx->yMin);
    pcx->xMax = LittleShort(pcx->xMax);
    pcx->yMax = LittleShort(pcx->yMax);
    pcx->hRes = LittleShort(pcx->hRes);
    pcx->vRes = LittleShort(pcx->vRes);
    pcx->bytesPerLine = LittleShort(pcx->bytesPerLine);
    pcx->paletteType = LittleShort(pcx->paletteType);

	in = &pcx->data;

	if (pcx->manufacturer != 0x0A || pcx->version != 5 || pcx->encoding != 1){
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadPCX: invalid PCX header (%s)\n", name);
		FS_FreeFile2(buffer);
		return false;
	}

	if (pcx->bitsPerPixel != 8 || pcx->colorPlanes != 1){
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadPCX: only 8 bit PCX images supported (%s)\n", name);
		FS_FreeFile2(buffer);
		return false;
	}
		
	if (pcx->xMax == 0 || pcx->yMax == 0 || pcx->xMax >= 640 || pcx->yMax >= 480){
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadPCX: bad PCX file (%i x %i) (%s)\n", pcx->xMax, pcx->yMax, name);
		FS_FreeFile2(buffer);
		return false;
	}

	if (palette){
		// Only the palette was requested
		*palette = (byte *)Mem_Alloc(768, rg.textureMemoryPool);
		memcpy(*palette, (byte *)buffer + len - 768, 768);

		FS_FreeFile2(buffer);
		return true;
	}

	*width = pcx->xMax+1;
	*height = pcx->yMax+1;

	*image = out = (byte *)Mem_Alloc((pcx->xMax+1) * (pcx->yMax+1) * 4, rg.textureMemoryPool);

	for (y = 0; y <= pcx->yMax; y++){
		for (x = 0; x <= pcx->xMax; ){
			dataByte = *in++;

			if ((dataByte & 0xC0) == 0xC0){
				runLength = dataByte & 0x3F;
				dataByte = *in++;
			}
			else
				runLength = 1;

			while (runLength-- > 0){
				*(unsigned *)out = r_palette[dataByte];

				if (out[0] != out[1] || out[1] != out[2])
					hasColor = true;

				if (out[3] != 255)
					hasAlpha = true;

				out += 4;
				x++;
			}
		}
	}

	if (in - buffer > len){
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadPCX: PCX file was malformed (%s)\n", name);
		FS_FreeFile2(buffer);
		Mem_Free(*image);
		*image = NULL;
		return false;
	}

	if (hasColor)
		*samples = (hasAlpha) ? 4 : 3;
	else
		*samples = (hasAlpha) ? 2 : 1;

	FS_FreeFile2(buffer);
	return true;
}


/*
 ==============================================================================

    WAL FORMAT LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadWAL
 ==================
*/
static bool R_LoadWAL (const char *name, byte **image, int *width, int *height, int *samples)
{
	mipTex_t   *header;
	byte	   *data;
	byte	   *in, *out;
	bool       hasColor = false, hasAlpha = false;
	int		   i, c;

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		return false;

	// Parse the WAL header
	header = (mipTex_t *)data;

	header->width = LittleLong(header->width);
	header->height = LittleLong(header->height);
	header->offsets[0] = LittleLong(header->offsets[0]);

	if (header->width == 0 || header->height == 0 || header->width > 4096 || header->height > 4096)
		Com_Error(ERR_DROP, "R_LoadWAL: bad image size (%i x %i) (%s)", header->width, header->height, name);

	// Skip ahead and read the image pixels
	in = data + header->offsets[0];

	*image = out = (byte *)Mem_Alloc(header->width * header->height * 4, rg.textureMemoryPool);

	*width = header->width;
	*height = header->height;

	c = header->width * header->height;

	for (i = 0; i < c; i++, in++, out += 4)
	{
		*(uint *)out = r_palette[*in];

		if (out[0] != out[1] || out[1] != out[2])
			hasColor = true;

		if (out[3] != 255)
			hasAlpha = true;
	}

	if (hasColor)
		*samples = (hasAlpha) ? 4 : 3;
	else
		*samples = (hasAlpha) ? 2 : 1;

	// Free file data
	FS_FreeFile2(data);

	return true;
}


/*
 ==============================================================================

    TARGA FORMAT LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadTGA
 ==================
*/
static bool R_LoadTGA (const char *name, byte **image, int *width, int *height, int *samples)
{
	tgaHeader_t   header;
	byte		  *data;
	byte		  *in, *out;
	bool          hasColor = false, hasAlpha = false;
	int			  x, y, stride;

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		return false;

	// Parse the TGA header
	header.bIdLength = data[0];
	header.bColormapType = data[1];
	header.bImageType = data[2];
	header.wColormapIndex = data[3] | (data[4] << 8);
	header.wColormapLength = data[5] | (data[6] << 8);
	header.bColormapSize = data[7];
	header.wXOrigin = data[8] | (data[9] << 8);
	header.wYOrigin = data[10] | (data[11] << 8);
	header.wWidth = data[12] | (data[13] << 8);
	header.wHeight = data[14] | (data[15] << 8);
	header.bPixelSize = data[16];
	header.bAttributes = data[17];

	if (header.bImageType != TGA_IMAGE_TRUECOLOR && header.bImageType != TGA_IMAGE_MONOCHROME)
		Com_Error(ERR_DROP, "R_LoadTGA: only type %i (RGB/RGBA) and %i (gray) images supported (%s)", TGA_IMAGE_TRUECOLOR, TGA_IMAGE_MONOCHROME, name);

	if (header.bPixelSize != 8 && header.bPixelSize != 24 && header.bPixelSize != 32)
		Com_Error(ERR_DROP, "R_LoadTGA: only 8 (gray), 24 (RGB), and 32 (RGBA) bit images supported (%s)", name);

	if (header.wWidth == 0 || header.wHeight == 0)
		Com_Error(ERR_DROP, "R_LoadTGA: bad image size (%i x %i) (%s)", header.wWidth, header.wHeight, name);

	// Skip ahead and read the image pixels
	in = data + 18 + header.bIdLength;

	if (header.bAttributes & BIT(5))
		stride = 0;
	else 
	{
		// Flipped image, read it backwards
		stride = -header.wWidth * (header.bPixelSize >> 3) * 2;

		in += (header.wHeight - 1) * header.wWidth * (header.bPixelSize >> 3);
	}

	*image = out = (byte *)Mem_Alloc(header.wWidth * header.wHeight * 4, rg.textureMemoryPool);

	*width = header.wWidth;
	*height = header.wHeight;

	for (y = 0; y < header.wHeight; y++)
	{
		for (x = 0; x < header.wWidth; x++)
		{
			switch (header.bPixelSize){
			case 8:
				out[0] = in[0];
				out[1] = in[0];
				out[2] = in[0];
				out[3] = 255;

				in += 1;
				break;
			case 24:
				out[0] = in[2];
				out[1] = in[1];
				out[2] = in[0];
				out[3] = 255;

				in += 3;
				break;
			case 32:
				out[0] = in[2];
				out[1] = in[1];
				out[2] = in[0];
				out[3] = in[3];

				in += 4;
				break;
			}

			if (out[0] != out[1] || out[1] != out[2])
				hasColor = true;

			if (out[3] != 255)
				hasAlpha = true;

			out += 4;
		}

		in += stride;
	}

	if (hasColor)
		*samples = (hasAlpha) ? 4 : 3;
	else
		*samples = (hasAlpha) ? 2 : 1;

	// Free file data
	FS_FreeFile2(data);

	return true;
}


/*
 ==============================================================================

    IMAGE PROGRAM FUNCTIONS

 ==============================================================================
*/


/*
 ==================
 R_FlipImage
 ==================
*/
static byte *R_FlipImage (byte *in, int width, int height, int samples, bool flipX, bool flipY, bool flipDiagonal)
{
	byte	*out;
	byte	*pIn, *pOut;
	byte	*line;
	int		xStride, xOffset;
	int		yStride, yOffset;
	int		i, x, y;

	out = pOut = (byte *)Mem_Alloc(width * height * samples, rg.textureMemoryPool);

	xStride = (flipX) ? -samples : samples;
	xOffset = (flipX) ? (width - 1) * samples : 0;

	yStride = (flipY) ? -samples * width : samples * width;
	yOffset = (flipY) ? (height - 1) * width * samples : 0;

	if (flipDiagonal)
	{
		for (x = 0, line = in + xOffset; x < width; x++, line += xStride)
		{
			for (y = 0, pIn = line + yOffset; y < height; y++, pIn += yStride, pOut += samples)
			{
				for (i = 0; i < samples; i++)
					pOut[i] = pIn[i];
			}
		}
	}
	else
	{
		for (y = 0, line = in + yOffset; y < height; y++, line += yStride)
		{
			for (x = 0, pIn = line + xOffset; x < width; x++, pIn += xStride, pOut += samples)
			{
				for (i = 0; i < samples; i++)
					pOut[i] = pIn[i];
			}
		}
	}

	Mem_Free(in);

	return out;
}


/*
 ==============================================================================

    TEXTURE REGISTRATION AND CREATION

 ==============================================================================
*/


/*
 ==================
 R_LoadImage
 ==================
*/
static byte *R_LoadImage (const char *name, int *width, int *height, int *samples, textureFlags_t *flags)
{
	char	baseName[MAX_OSPATH], loadName[MAX_OSPATH];
	byte	*image;

	Com_StripExtension(name, baseName, sizeof(baseName));

	Str_SPrintf(loadName, sizeof(loadName), "%s.tga", baseName);
	if (R_LoadTGA(loadName, &image, width, height, samples))
		return image;

	Str_SPrintf(loadName, sizeof(loadName), "%s.pcx", baseName);
	if (R_LoadPCX(loadName, &image, NULL, width, height, samples))
		return image;

	Str_SPrintf(loadName, sizeof(loadName), "%s.wal", baseName);
	if (R_LoadWAL(loadName, &image, width, height, samples))
		return image;

	// Not found or invalid
	return NULL;
}

/*
 ==================
 R_ResampleTexture
 ==================
*/
static void R_ResampleTexture (uint *in, int inWidth, int inHeight, uint *out, int outWidth, int outHeight, bool isNormalMap)
{
	uint	frac, fracStep;
	uint	p1[0x1000], p2[0x1000];
	uint	*inRow1, *inRow2;
	byte	*pix1, *pix2, *pix3, *pix4;
	vec3_t	normal;
	int		i, x, y;

	fracStep = inWidth * 0x10000 / outWidth;

	frac = fracStep >> 2;
	for (i = 0; i < outWidth; i++)
	{
		p1[i] = 4 * (frac >> 16);
		frac += fracStep;
	}

	frac = (fracStep >> 2) * 3;
	for (i = 0; i < outWidth; i++)
	{
		p2[i] = 4 * (frac >> 16);
		frac += fracStep;
	}

	if (isNormalMap)
	{
		for (y = 0; y < outHeight; y++, out += outWidth)
		{
			inRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight/outHeight);
			inRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight/outHeight);

			for (x = 0; x < outWidth; x++)
			{
				pix1 = (byte *)inRow1 + p1[x];
				pix2 = (byte *)inRow1 + p2[x];
				pix3 = (byte *)inRow2 + p1[x];
				pix4 = (byte *)inRow2 + p2[x];

				normal[0] = (pix1[0] * (1.0f/127) - 1.0f) + (pix2[0] * (1.0f/127) - 1.0f) + (pix3[0] * (1.0f/127) - 1.0f) + (pix4[0] * (1.0f/127) - 1.0f);
				normal[1] = (pix1[1] * (1.0f/127) - 1.0f) + (pix2[1] * (1.0f/127) - 1.0f) + (pix3[1] * (1.0f/127) - 1.0f) + (pix4[1] * (1.0f/127) - 1.0f);
				normal[2] = (pix1[2] * (1.0f/127) - 1.0f) + (pix2[2] * (1.0f/127) - 1.0f) + (pix3[2] * (1.0f/127) - 1.0f) + (pix4[2] * (1.0f/127) - 1.0f);

				if (!VectorNormalize(normal))
					VectorSet(normal, 0.0f, 0.0f, 1.0f);

				((byte *)(out+x))[0] = (byte)(128 + 127 * normal[0]);
				((byte *)(out+x))[1] = (byte)(128 + 127 * normal[1]);
				((byte *)(out+x))[2] = (byte)(128 + 127 * normal[2]);
				((byte *)(out+x))[3] = 255;
			}
		}
	}
	else
	{
		for (y = 0; y < outHeight; y++, out += outWidth)
		{
			inRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight/outHeight);
			inRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight/outHeight);

			for (x = 0; x < outWidth; x++)
			{
				pix1 = (byte *)inRow1 + p1[x];
				pix2 = (byte *)inRow1 + p2[x];
				pix3 = (byte *)inRow2 + p1[x];
				pix4 = (byte *)inRow2 + p2[x];

				((byte *)(out+x))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2;
				((byte *)(out+x))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2;
				((byte *)(out+x))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2;
				((byte *)(out+x))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3]) >> 2;
			}
		}
	}
}

/*
 ==================
 R_BuildMipMap
 ==================
*/
static void R_BuildMipMap (byte *in, int width, int height, bool isNormalMap)
{
	byte	*out = in;
	vec3_t	normal;
	int		x, y;

	width <<= 2;
	height >>= 1;

	if (isNormalMap)
	{
		for (y = 0; y < height; y++, in += width)
		{
			for (x = 0; x < width; x += 8, in += 8, out += 4)
			{
				normal[0] = (in[0] * (1.0f/127) - 1.0f) + (in[4] * (1.0f/127) - 1.0f) + (in[width+0] * (1.0f/127) - 1.0) + (in[width+4] * (1.0f/127) - 1.0f);
				normal[1] = (in[1] * (1.0f/127) - 1.0f) + (in[5] * (1.0f/127) - 1.0f) + (in[width+1] * (1.0f/127) - 1.0) + (in[width+5] * (1.0f/127) - 1.0f);
				normal[2] = (in[2] * (1.0f/127) - 1.0f) + (in[6] * (1.0f/127) - 1.0f) + (in[width+2] * (1.0f/127) - 1.0) + (in[width+6] * (1.0f/127) - 1.0f);

				if (!VectorNormalize(normal))
					VectorSet(normal, 0.0f, 0.0f, 1.0f);

				out[0] = (byte)(128 + 127 * normal[0]);
				out[1] = (byte)(128 + 127 * normal[1]);
				out[2] = (byte)(128 + 127 * normal[2]);
				out[3] = 255;
			}
		}
	}
	else
	{
		for (y = 0; y < height; y++, in += width)
		{
			for (x = 0; x < width; x += 8, in += 8, out += 4)
			{
				out[0] = (in[0] + in[4] + in[width+0] + in[width+4]) >> 2;
				out[1] = (in[1] + in[5] + in[width+1] + in[width+5]) >> 2;
				out[2] = (in[2] + in[6] + in[width+2] + in[width+6]) >> 2;
				out[3] = (in[3] + in[7] + in[width+3] + in[width+7]) >> 2;
			}
		}
	}
}

/*
 ==================
 R_ForceTextureBorder
 ==================
*/
static void R_ForceTextureBorder (byte *in, int width, int height, bool hasAlpha)
{
	byte	*out = in;
	uint	borderColor;
	int		i;

	if (hasAlpha)
		borderColor = 0x00000000;
	else
		borderColor = 0xFF000000;

	for (i = 0, out = in; i < width; i++, out += 4)
		*(uint *)out = borderColor;
	for (i = 0, out = in + (4 * ((height - 1) * width)); i < width; i++, out += 4)
		*(uint *)out = borderColor;
	for (i = 0, out = in; i < height; i++, out += 4 * width)
		*(uint *)out = borderColor;
	for (i = 0, out = in + (4 * (width - 1)); i < height; i++, out += 4 * width)
		*(uint *)out = borderColor;
}

/*
 ==================
 R_CalcTextureSize
 ==================
*/
static int R_CalcTextureSize (uint target, int level, int width, int height, int samples, bool useCompression)
{
	GLint	isCompressed;
	int		size;

	if (useCompression)
	{
		qglGetTexLevelParameteriv(target, level, GL_TEXTURE_COMPRESSED_ARB, (GLint*)&isCompressed);

		if (isCompressed)
		{
			qglGetTexLevelParameteriv(target, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, &size);
			return size;
		}
	}

	if (samples >= 3)
		size = width * height * 4;
	else
		size = width * height * samples;

	return size;
}

/*
 ==================
 R_UploadTexture
 ==================
*/
static void R_UploadTexture (byte **image, texture_t *texture)
{
	vec4_t	zeroBorder = {0, 0, 0, 1}, zeroAlphaBorder = {0, 0, 0, 0};
	bool	useCompression, forceBorder;
	int		texFaces;
	int		mipLevel, mipWidth, mipHeight;
	int		i;
	byte	*texImage;
	uint	texTarget;

	// Find nearest power of two, rounding down if desired
	texture->uploadWidth = NearestPowerOfTwo(texture->sourceWidth, r_roundTexturesDown->integerValue);
	texture->uploadHeight = NearestPowerOfTwo(texture->sourceHeight, r_roundTexturesDown->integerValue);

	// Sample down if desired
	if (texture->flags & TF_NORMALMAP)
	{
		if (r_downSizeNormalTextures->integerValue && !(texture->flags & TF_NOPICMIP))
		{
			while (texture->uploadWidth > r_maxNormalTextureSize->integerValue || texture->uploadHeight > r_maxNormalTextureSize->integerValue){
				texture->uploadWidth >>= 1;
				texture->uploadHeight >>= 1;
			}
		}
	}
	else
	{
		if (r_downSizeTextures->integerValue && !(texture->flags & TF_NOPICMIP))
		{
			while (texture->uploadWidth > r_maxTextureSize->integerValue || texture->uploadHeight > r_maxTextureSize->integerValue){
				texture->uploadWidth >>= 1;
				texture->uploadHeight >>= 1;
			}
		}
	}

	// Clamp to hardware limits
	if (texture->isCubeMap)
	{
		while (texture->uploadWidth > glConfig.maxCubeMapTextureSize || texture->uploadHeight > glConfig.maxCubeMapTextureSize){
			texture->uploadWidth >>= 1;
			texture->uploadHeight >>= 1;
		}
	}
	else
	{
		while (texture->uploadWidth > glConfig.maxTextureSize || texture->uploadHeight > glConfig.maxTextureSize){
			texture->uploadWidth >>= 1;
			texture->uploadHeight >>= 1;
		}
	}

	if (texture->uploadWidth < 1)
		texture->uploadWidth = 1;
	if (texture->uploadHeight < 1)
		texture->uploadHeight = 1;

	texture->uploadSize = 0;

	useCompression = false;
#if 0
	// Check if it should be compressed
	if (texture->flags & TF_NORMALMAP)
	{
		if (!r_compressNormalTextures->integerValue || (texture->flags & TF_UNCOMPRESSED))
			useCompression = false;
		else
			useCompression = glConfig.textureCompression;
	}
	else
	{
		if (!r_compressTextures->integerValue || (texture->flags & TF_UNCOMPRESSED))
			useCompression = false;
		else
			useCompression = glConfig.textureCompression;
	}
#endif
	// Check if it needs a border
	if (texture->wrap == TW_CLAMP_TO_ZERO || texture->wrap == TW_CLAMP_TO_ZERO_ALPHA)
		forceBorder = !glConfig.textureBorderClamp;
	else
		forceBorder = false;

	// Set texture format
	if (useCompression)
	{
		switch (texture->sourceSamples){
		case 1:		texture->uploadFormat = GL_COMPRESSED_LUMINANCE_ARB;		break;
		case 2:		texture->uploadFormat = GL_COMPRESSED_LUMINANCE_ALPHA_ARB;	break;
		case 3:		texture->uploadFormat = GL_COMPRESSED_RGB_ARB;				break;
		case 4:		texture->uploadFormat = GL_COMPRESSED_RGBA_ARB;				break;
		}

		if (texture->flags & TF_INTENSITY)
			texture->uploadFormat = GL_COMPRESSED_INTENSITY_ARB;
		if (texture->flags & TF_ALPHA)
			texture->uploadFormat = GL_COMPRESSED_ALPHA_ARB;

		texture->flags &= ~TF_INTENSITY;
		texture->flags &= ~TF_ALPHA;
	}
	else
	{
		switch (texture->sourceSamples){
		case 1:		texture->uploadFormat = GL_LUMINANCE8;						break;
		case 2:		texture->uploadFormat = GL_LUMINANCE8_ALPHA8;				break;
		case 3:		texture->uploadFormat = GL_RGB8;							break;
		case 4:		texture->uploadFormat = GL_RGBA8;							break;
		}

		if (texture->flags & TF_INTENSITY)
			texture->uploadFormat = GL_INTENSITY8;
		if (texture->flags & TF_ALPHA)
			texture->uploadFormat = GL_ALPHA8;

		texture->flags &= ~TF_INTENSITY;
		texture->flags &= ~TF_ALPHA;
	}

	// Set texture target
	if (texture->isCubeMap)
	{
		texture->uploadTarget = GL_TEXTURE_CUBE_MAP_ARB;

		texTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
		texFaces = 6;
	}
	else
	{
		texture->uploadTarget = GL_TEXTURE_2D;

		texTarget = GL_TEXTURE_2D;
		texFaces = 1;
	}

	// Bind the texture
	qglGenTextures(1, &texture->texNum);

	GL_BindTexture(texture);

	// Upload all the faces
	for (i = 0; i < texFaces; i++)
	{
		// Copy or resample the texture
		if (texture->uploadWidth == texture->sourceWidth && texture->uploadHeight == texture->sourceHeight)
			texImage = image[i];
		else
		{
			texImage = (byte*)Mem_Alloc(texture->uploadWidth * texture->uploadHeight * 4, rg.textureMemoryPool);

			R_ResampleTexture((uint*)(image[i]), texture->sourceWidth, texture->sourceHeight, (uint*)texImage, texture->uploadWidth, texture->uploadHeight, (texture->flags & TF_NORMALMAP) != 0);
		}

		// Make sure it has a border if needed
		if (forceBorder)
			R_ForceTextureBorder(texImage, texture->uploadWidth, texture->uploadHeight, (texture->wrap == TW_CLAMP_TO_ZERO_ALPHA));

		// Upload the base texture
		qglTexImage2D(texTarget + i, 0, texture->uploadFormat, texture->uploadWidth, texture->uploadHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, texImage);

		// Calculate size
		texture->uploadSize += R_CalcTextureSize(texTarget + i, 0, texture->uploadWidth, texture->uploadHeight, texture->sourceSamples, useCompression);

		// Generate mipmaps if desired
		if (texture->filter == TF_DEFAULT)
		{
			mipLevel = 0;
			mipWidth = texture->uploadWidth;
			mipHeight = texture->uploadHeight;

			while (mipWidth > 1 || mipHeight > 1){
				// Development tool
				if (r_showColorMipLevels->integerValue)
				{
					if (!(texture->flags & (TF_INTERNAL | TF_NORMALMAP)) && texture->sourceSamples >= 3)
					{
						qglPixelTransferf(GL_RED_BIAS, (mipLevel % 3 == 0) ? 1.0 : 0.0);
						qglPixelTransferf(GL_GREEN_BIAS, (mipLevel % 3 == 1) ? 1.0 : 0.0);
						qglPixelTransferf(GL_BLUE_BIAS, (mipLevel % 3 == 2) ? 1.0 : 0.0);
					}
				}

				// Build the mipmap
				R_BuildMipMap(texImage, mipWidth, mipHeight, (texture->flags & TF_NORMALMAP) != 0);

				mipLevel++;

				mipWidth >>= 1;
				if (mipWidth < 1)
					mipWidth = 1;

				mipHeight >>= 1;
				if (mipHeight < 1)
					mipHeight = 1;

				// Make sure it has a border if needed
				if (forceBorder)
					R_ForceTextureBorder(texImage, mipWidth, mipHeight, (texture->wrap == TW_CLAMP_TO_ZERO_ALPHA));

				// Upload the mipmap texture
				qglTexImage2D(texTarget + i, mipLevel, texture->uploadFormat, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, texImage);

				// Calculate size
				texture->uploadSize += R_CalcTextureSize(texTarget + i, mipLevel, mipWidth, mipHeight, texture->sourceSamples, useCompression);
			}

			if (r_showColorMipLevels->integerValue)
			{
				if (!(texture->flags & (TF_INTERNAL | TF_NORMALMAP)) && texture->sourceSamples >= 3)
				{
					qglPixelTransferf(GL_RED_BIAS, 0.0f);
					qglPixelTransferf(GL_GREEN_BIAS, 0.0f);
					qglPixelTransferf(GL_BLUE_BIAS, 0.0f);
				}
			}
		}

		// Free texture data
		if (texImage != image[i])
			Mem_Free(texImage);
	}

	// Set texture filter
	switch (texture->filter){
	case TF_DEFAULT:
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		// Set texture anisotropy if available
		if (glConfig.textureFilterAnisotropic)
			qglTexParameterf(texture->uploadTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_textureAnisotropy->floatValue);

		// Set texture LOD bias if available
		if (glConfig.textureLODBias)
			qglTexParameterf(texture->uploadTarget, GL_TEXTURE_LOD_BIAS_EXT, r_textureLODBias->floatValue);

		break;
	case TF_NEAREST:
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

		break;
	case TF_LINEAR:
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		break;
	default:
		Com_Error(ERR_DROP, "R_UploadTexture: bad texture filter (%i)", texture->filter);
	}

	// Set texture wrap
	switch (texture->wrap){
	case TW_REPEAT:
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_S, GL_REPEAT);
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_T, GL_REPEAT);

		break;
	case TW_CLAMP:
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		break;
	case TW_CLAMP_TO_ZERO:
		if (glConfig.textureBorderClamp)
		{
			qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB);
			qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER_ARB);
		}
		else
		{
			qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
			qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_T, GL_CLAMP);
		}

		qglTexParameterfv(texture->uploadTarget, GL_TEXTURE_BORDER_COLOR, zeroBorder);

		break;
	case TW_CLAMP_TO_ZERO_ALPHA:
		if (glConfig.textureBorderClamp)
		{
			qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB);
			qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER_ARB);
		}
		else
		{
			qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
			qglTexParameteri(texture->uploadTarget, GL_TEXTURE_WRAP_T, GL_CLAMP);
		}

		qglTexParameterfv(texture->uploadTarget, GL_TEXTURE_BORDER_COLOR, zeroAlphaBorder);

		break;
	default:
		Com_Error(ERR_DROP, "R_UploadTexture: bad texture wrap (%i)", texture->wrap);
	}
}

/*
 ==================
 R_LoadTexture
 ==================
*/
static texture_t *R_LoadTexture (const char *name, byte *image, int width, int height, int samples, int flags, textureFilter_t filter, textureWrap_t wrap)
{
	texture_t   *texture;
	uint	    hashKey;

	// Allocate a new texture
	if (r_numTextures == MAX_TEXTURES)
		Com_Error(ERR_DROP, "R_LoadTexture: MAX_TEXTURES hit");

	r_textures[r_numTextures++] = texture = (texture_t *)Mem_Alloc(sizeof(texture_t), rg.textureMemoryPool);

	// Fill it in
	Str_Copy(texture->name, name, sizeof(texture->name));
	texture->flags = flags;
	texture->filter = filter;
	texture->wrap = wrap;
	texture->isCubeMap = false;
	texture->frameCount = 0;
	texture->sourceWidth = width;
	texture->sourceHeight = height;
	texture->sourceSamples = samples;

	// Create it
	R_UploadTexture(&image, texture);

	// Add to hash table
	hashKey = Str_HashKey(texture->name, TEXTURES_HASH_SIZE, false);

	texture->nextHash = r_texturesHashTable[hashKey];
	r_texturesHashTable[hashKey] = texture;

	return texture;
}

/*
 ==================
 R_LoadCubeMapTexture
 ==================
*/
static texture_t *R_LoadCubeMapTexture (const char *name, byte **images, int width, int height, int samples, int flags, textureFilter_t filter, textureWrap_t wrap)
{
	texture_t   *texture;
	uint	    hashKey;

	// Allocate a new texture
	if (r_numTextures == MAX_TEXTURES)
		Com_Error(ERR_DROP, "R_LoadCubeMapTexture: MAX_TEXTURES hit");

	r_textures[r_numTextures++] = texture = (texture_t *)Mem_Alloc(sizeof(texture_t), rg.textureMemoryPool);

	// Fill it in
	Str_Copy(texture->name, name, sizeof(texture->name));
	texture->flags = flags;
	texture->filter = filter;
	texture->wrap = wrap;
	texture->isCubeMap = true;
	texture->frameCount = 0;
	texture->sourceWidth = width;
	texture->sourceHeight = height;
	texture->sourceSamples = samples;

	// Create it
	R_UploadTexture(images, texture);

	// Add to hash table
	hashKey = Str_HashKey(texture->name, TEXTURES_HASH_SIZE, false);

	texture->nextHash = r_texturesHashTable[hashKey];
	r_texturesHashTable[hashKey] = texture;

	return texture;
}

/*
 ==================
 R_FindTexture
 ==================
*/
texture_t *R_FindTexture (const char *name, textureFlags_t flags, textureFilter_t filter, textureWrap_t wrap)
{
	texture_t   *texture;
	byte		*image;
	int			width, height, samples;
	uint	    hashKey;

	if (!name || !name[0])
		Com_Error(ERR_DROP, "R_FindTexture: NULL texture name");

	if (Str_Length(name) >= MAX_PATH_LENGTH)
		Com_Error(ERR_DROP, "R_FindTexture: texture name exceeds MAX_PATH_LENGTH");

	// Check if it already exists
	hashKey = Str_HashKey(name, TEXTURES_HASH_SIZE, false);

	for (texture = r_texturesHashTable[hashKey]; texture; texture = texture->nextHash)
	{
		if (texture->isCubeMap)
			continue;

		if (!Str_ICompare(texture->name, name))
		{
			if (texture->flags & TF_INTERNAL)
				return texture;

			if (texture->flags != flags)
				Com_Printf(S_COLOR_YELLOW "WARNING: reused texture '%s' with mixed flags parameter\n", name);
			if (texture->filter != filter)
				Com_Printf(S_COLOR_YELLOW "WARNING: reused texture '%s' with mixed filter parameter\n", name);
			if (texture->wrap != wrap)
				Com_Printf(S_COLOR_YELLOW "WARNING: reused texture '%s' with mixed wrap parameter\n", name);

			return texture;
		}
	}

	// Load image data
	image = R_LoadImage(name, &width, &height, &samples, &flags);

	// Create the texture with the image data
	if (image)
	{
		texture = R_LoadTexture(name, image, width, height, samples, flags, filter, wrap);
		Mem_Free(image);
		return texture;
	}

	// Not found or invalid
	return NULL;
}

/*
 ==================
 R_FindCubeMapTexture
 ==================
*/
texture_t *R_FindCubeMapTexture (const char *name, textureFlags_t flags, textureFilter_t filter, textureWrap_t wrap, bool cameraSpace){

	texture_t	*texture;
	char		loadName[MAX_OSPATH];
	byte		*images[6];
	int			width, height, samples;
	int			cubeWidth, cubeHeight, cubeSamples;
	unsigned	hash;
	int			i, j;

	if (!name || !name[0])
		Com_Error(ERR_DROP, "R_FindCubeMapTexture: NULL texture name");

	if (strlen(name) >= MAX_OSPATH)
		Com_Error(ERR_DROP, "R_FindCubeMapTexture: texture name exceeds MAX_OSPATH");

	// See if already loaded
	hash = Str_HashKey(name, TEXTURES_HASH_SIZE, false);

	for (texture = r_texturesHashTable[hash]; texture; texture = texture->nextHash){
		if (!texture->isCubeMap)
			continue;

		if (!Q_stricmp(texture->name, name)){
			if (texture->flags & TF_INTERNAL)
				return texture;

			if (texture->flags != flags)
				Com_Printf(S_COLOR_YELLOW "WARNING: reused texture '%s' with mixed flags parameter\n", name);
			if (texture->filter != filter)
				Com_Printf(S_COLOR_YELLOW "WARNING: reused texture '%s' with mixed filter parameter\n", name);
			if (texture->wrap != wrap)
				Com_Printf(S_COLOR_YELLOW "WARNING: reused texture '%s' with mixed wrap parameter\n", name);

			return texture;
		}
	}

	// Load it from disk
	for (i = 0; i < 6; i++){
		Str_SPrintf(loadName, sizeof(loadName), "%s%s%s.tga", name, r_cubeSides[cameraSpace][i].separator, r_cubeSides[cameraSpace][i].suffix);
		if (!R_LoadTGA(loadName, &images[i], &width, &height, &samples))
			break;

		// Check dimensions
		if (width != height){
			Com_Printf(S_COLOR_YELLOW "WARNING: cube map face '%s%s%s' is not square (%i != %i)\n", name, r_cubeSides[cameraSpace][i].separator, r_cubeSides[cameraSpace][i].suffix, width, height);
			break;
		}

		// Compare dimensions and format with previous faces
		if (i != 0){
			if (width != cubeWidth || height != cubeHeight){
				Com_Printf(S_COLOR_YELLOW "WARNING: cube map face '%s%s%s' has mismatched dimensions (%i x %i != %i x %i)\n", name, r_cubeSides[cameraSpace][i].separator, r_cubeSides[cameraSpace][i].suffix, width, height, cubeWidth, cubeHeight);
				break;
			}

			if (samples != cubeSamples){
				if (samples == 2){
					if (cubeSamples == 1)
						cubeSamples = 2;
					else if (cubeSamples == 3)
						cubeSamples = 4;
				}
				else if (samples == 3){
					if (cubeSamples == 1)
						cubeSamples = 3;
					else if (cubeSamples == 2)
						cubeSamples = 4;
				}
				else if (samples == 4)
					cubeSamples = 4;
			}

			continue;
		}

		cubeWidth = width;
		cubeHeight = height;
		cubeSamples = samples;
	}

	// Load the texture
	if (i == 6){
		for (i = 0; i < 6; i++){
			if (r_cubeSides[cameraSpace][i].flipX || r_cubeSides[cameraSpace][i].flipY || r_cubeSides[cameraSpace][i].flipDiagonal)
				images[i] = R_FlipImage(images[i], cubeWidth, cubeHeight, 4, r_cubeSides[cameraSpace][i].flipX, r_cubeSides[cameraSpace][i].flipY, r_cubeSides[cameraSpace][i].flipDiagonal);
		}

		texture = R_LoadCubeMapTexture(name, images, cubeWidth, cubeHeight, cubeSamples, flags, filter, wrap);

		for (i = 0; i < 6; i++)
			Mem_Free(images[i]);

		return texture;
	}

	// Not found or invalid
	for (j = 0; j < i; j++)
		Mem_Free(images[j]);

	return NULL;
}


/*
 ==============================================================================

    CAPTURE RENDERER TO TEXTURE

 ==============================================================================
*/


/*
 ==================
 R_CaptureRenderToTexture
 ==================
*/
bool R_CaptureRenderToTexture (const char *name)
{
	captureRenderCommand_t   *cmd;
	texture_t				 *texture;
	uint					 hashKey;

	if (!name || !name[0])
		Com_Error(ERR_DROP, "R_CaptureRenderToTexture: NULL texture name");

	if (Str_Length(name) >= MAX_PATH_LENGTH)
		Com_Error(ERR_DROP, "R_CaptureRenderToTexture: texture name exceeds MAX_PATH_LENGTH");

	// Find the texture
	hashKey = Str_HashKey(name, TEXTURES_HASH_SIZE, false);

	for (texture = r_texturesHashTable[hashKey]; texture; texture = texture->nextHash)
	{
		if (texture->isCubeMap)
			continue;

		if (!Str_ICompare(texture->name, name))
			break;
	}

	if (!texture)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_CaptureRenderToTexture: couldn't find texture (%s)\n", name);
		return false;
	}

	// Make sure it may not be down-sized or compressed
	if (!(texture->flags & TF_NOPICMIP) || !(texture->flags & TF_UNCOMPRESSED))
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_CaptureRenderToTexture: texture may be down-sized or compressed (%s)\n", name);
		return false;
	}

	// Make sure it's not mipmapped
	if (texture->filter == TF_DEFAULT)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_CaptureRenderToTexture: texture has mipmaps (%s)\n", name);
		return false;
	}

	// Make sure it doesn't have a border
	if (texture->wrap != TW_REPEAT && texture->wrap != TW_CLAMP)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_CaptureRenderToTexture: texture has a border (%s)\n", name);
		return false;
	}

	// Add a capture render command
	cmd = (captureRenderCommand_t*)RB_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return true;

	cmd->commandId = RC_CAPTURE_RENDER;

	cmd->texture = texture;

	return true;
}

/*
 ==================
 R_UpdateTexture
 ==================
*/
bool R_UpdateTexture (const char *name, const byte *image, int width, int height)
{
	updateTextureCommand_t   *cmd;
	texture_t				 *texture;
	uint					 hashKey;

	if (!name || !name[0])
		Com_Error(ERR_DROP, "R_UpdateTexture: NULL texture name");

	if (Str_Length(name) >= MAX_PATH_LENGTH)
		Com_Error(ERR_DROP, "R_UpdateTexture: texture name exceeds MAX_OSPATH");

	// Find the texture
	hashKey = Str_HashKey(name, TEXTURES_HASH_SIZE, false);

	for (texture = r_texturesHashTable[hashKey]; texture; texture = texture->nextHash)
	{
		if (texture->isCubeMap)
			continue;

		if (!Str_ICompare(texture->name, name))
			break;
	}

	if (!texture)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_UpdateTexture: couldn't find texture (%s)\n", name);
		return false;
	}

	// Make sure it may not be down-sized or compressed
	if (!(texture->flags & TF_NOPICMIP) || !(texture->flags & TF_UNCOMPRESSED))
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_UpdateTexture: texture may be down-sized or compressed (%s)\n", name);
		return false;
	}

	// Make sure it's not mipmapped
	if (texture->filter == TF_DEFAULT)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_UpdateTexture: texture has mipmaps (%s)\n", name);
		return false;
	}

	// Make sure it doesn't have a border
	if (texture->wrap != TW_REPEAT && texture->wrap != TW_CLAMP)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_UpdateTexture: texture has a border (%s)\n", name);
		return false;
	}

	// Add an update texture command
	cmd = (updateTextureCommand_t*)RB_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return true;

	cmd->commandId = RC_UPDATE_TEXTURE;

	cmd->texture = texture;

	cmd->image = image;
	cmd->width = width;
	cmd->height = height;

	return true;
}


/*
 ==============================================================================

    BUILT-IN TEXTURES AND PARAMETERS

 ==============================================================================
*/


/*
 ==================
 R_UpdateTextureParameters

 Called in R_BeginFrame for updating if any variables were changed
 during the game
 ==================
*/
void R_UpdateTextureParameters ()
{
	texture_t	*texture;
	int			i;

	if (glConfig.textureFilterAnisotropic)
	{
		if (r_textureAnisotropy->floatValue > glConfig.maxTextureMaxAnisotropy)
			CVar_SetFloat(r_textureAnisotropy, glConfig.maxTextureMaxAnisotropy);
		else if (r_textureAnisotropy->floatValue < 1.0f)
			CVar_SetFloat(r_textureAnisotropy, 1.0f);
	}

	r_textureAnisotropy->modified = false;

	if (glConfig.textureLODBias)
	{
		if (r_textureLODBias->floatValue > glConfig.maxTextureLODBias)
			CVar_SetFloat(r_textureLODBias, glConfig.maxTextureLODBias);
		else if (r_textureLODBias->floatValue < -glConfig.maxTextureLODBias)
			CVar_SetFloat(r_textureLODBias, -glConfig.maxTextureLODBias);
	}

	r_textureLODBias->modified = false;

	// Change all the existing mipmapped texture objects
	for (i = 0; i < r_numTextures; i++)
	{
		texture = r_textures[i];

		if (texture->filter != TF_DEFAULT)
			continue;

		GL_BindTexture(texture);

		// Set texture filter
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		qglTexParameteri(texture->uploadTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		// Set texture anisotropy if available
		if (glConfig.textureFilterAnisotropic)
			qglTexParameterf(texture->uploadTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_textureAnisotropy->floatValue);

		// Set texture LOD bias if available
		if (glConfig.textureLODBias)
			qglTexParameterf(texture->uploadTarget, GL_TEXTURE_LOD_BIAS_EXT, r_textureLODBias->floatValue);
	}
}

/*
 ==================
 R_CreateBuiltInTextures
 ==================
*/
static void R_CreateBuiltInTextures ()
{
	byte	data2D[256*256*4];
	vec3_t	normal;
	float	s, t, intensity;
	int		i, x, y;

	// Default texture
	for (y = 0; y < 16; y++)
	{
		for (x = 0; x < 16; x++)
		{
			if (x == 0 || x == 15 || y == 0 || y == 15)
				((uint *)&data2D)[y*16+x] = LittleLong(0xFFFFFFFF);
			else
				((uint *)&data2D)[y*16+x] = LittleLong(0xFF000000);
		}
	}

	rg.defaultTexture = R_LoadTexture("_default", data2D, 16, 16, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_DEFAULT, TW_REPEAT);

	// White texture
	for (i = 0; i < 64; i++)
		((uint *)&data2D)[i] = LittleLong(0xFFFFFFFF);

	rg.whiteTexture = R_LoadTexture("_white", data2D, 8, 8, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_DEFAULT, TW_REPEAT);

	// Black texture
	for (i = 0; i < 64; i++)
		((uint *)&data2D)[i] = LittleLong(0xFF000000);

	rg.blackTexture = R_LoadTexture("_black", data2D, 8, 8, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_DEFAULT, TW_REPEAT);

	// Flat texture
	for (i = 0; i < 64; i++)
		((uint *)&data2D)[i] = LittleLong(0xFFFF8080);

	rg.flatTexture = R_LoadTexture("_flat", data2D, 8, 8, 3, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED | TF_NORMALMAP, TF_DEFAULT, TW_REPEAT);

	// Attenuation texture
	for (y = 0; y < 128; y++)
	{
		for (x = 0; x < 128; x++)
		{
			s = ((((float)x + 0.5f) * (2.0f/128)) - 1.0f) * (1.0f/0.9375f);
			t = ((((float)y + 0.5f) * (2.0f/128)) - 1.0f) * (1.0f/0.9375f);

			intensity = 1.0f - sqrt(s*s + t*t);

			intensity = Clamp(255.0f * intensity, 0.0f, 255.0f);

			data2D[4*(y*128+x)+0] = (byte)intensity;
			data2D[4*(y*128+x)+1] = (byte)intensity;
			data2D[4*(y*128+x)+2] = (byte)intensity;
			data2D[4*(y*128+x)+3] = 255;
		}
	}

	rg.attenuationTexture = R_LoadTexture("_attenuation", data2D, 128, 128, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_DEFAULT, TW_CLAMP_TO_ZERO);

	// No attenuation texture
	memset(data2D, 255, sizeof(data2D));

	rg.noAttenuationTexture = R_LoadTexture("_noAttenuation", data2D, 128, 128, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_DEFAULT, TW_CLAMP_TO_ZERO);

	// Falloff texture
	for (y = 0; y < 8; y++)
	{
		for (x = 0; x < 64; x++)
		{
			s = ((((float)x + 0.5f) * (2.0f/64)) - 1.0f) * (1.0f/0.9375f);

			intensity = 1.0f - sqrt(s*s);

			intensity = Clamp(255.0f * intensity, 0.0f, 255.0f);

			data2D[4*(y*64+x)+0] = (byte)intensity;
			data2D[4*(y*64+x)+1] = (byte)intensity;
			data2D[4*(y*64+x)+2] = (byte)intensity;
			data2D[4*(y*64+x)+3] = (byte)intensity;
		}
	}

	rg.falloffTexture = R_LoadTexture("_falloff", data2D, 64, 8, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED | TF_INTENSITY, TF_LINEAR, TW_CLAMP);

	// No falloff texture
	memset(data2D, 255, sizeof(data2D));

	rg.noFalloffTexture = R_LoadTexture("_noFalloff", data2D, 64, 4, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED | TF_INTENSITY, TF_LINEAR, TW_CLAMP);

	// Fog texture
	for (y = 0; y < 128; y++)
	{
		for (x = 0; x < 128; x++)
		{
			s = ((((float)x + 0.5f) * (2.0f/128)) - 1.0f) * (1.0f/0.9375f);
			t = ((((float)y + 0.5f) * (2.0f/128)) - 1.0f) * (1.0f/0.9375f);

			intensity = pow(sqrt(s*s + t*t), 0.5f);

			intensity = Clamp(255.0 * intensity, 0.0f, 255.0f);

			data2D[4*(y*128+x)+0] = 255;
			data2D[4*(y*128+x)+1] = 255;
			data2D[4*(y*128+x)+2] = 255;
			data2D[4*(y*128+x)+3] = (byte)intensity;
		}
	}

	rg.fogTexture = R_LoadTexture("_fog", data2D, 128, 128, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED | TF_ALPHA, TF_LINEAR, TW_CLAMP);

	// Fog-enter texture
	for (y = 0; y < 64; y++)
	{
		for (x = 0; x < 64; x++)
		{
			s = ((((float)x + 0.5f) * (2.0f/64)) - 1.0f) * (1.0f/0.3750f);
			t = ((((float)y + 0.5f) * (2.0f/64)) - 1.0f) * (1.0f/0.3750f);

			s = Clamp(s + (1.0/16), -1.0f, 0.0f);
			t = Clamp(t + (1.0/16), -1.0f, 0.0f);

			intensity = pow(sqrt(s*s + t*t), 0.5f);

			intensity = Clamp(255.0f * intensity, 0.0f, 255.0f);

			data2D[4*(y*64+x)+0] = 255;
			data2D[4*(y*64+x)+1] = 255;
			data2D[4*(y*64+x)+2] = 255;
			data2D[4*(y*64+x)+3] = (byte)intensity;
		}
	}

	rg.fogEnterTexture = R_LoadTexture("_fogEnter", data2D, 64, 64, 1, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED | TF_ALPHA, TF_LINEAR, TW_CLAMP);

	// Cinematic texture
	memset(data2D, 255, 16*16*4);
	rg.cinematicTexture = R_LoadTexture("_cinematic", data2D, 16, 16, 4, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_LINEAR, TW_REPEAT);

	// Scratch texture
	memset(data2D, 255, 16*16*4);
	rg.scratchTexture = R_LoadTexture("_scratch", data2D, 16, 16, 4, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_LINEAR, TW_REPEAT);

	// Accum texture
	memset(data2D, 255, 16*16*4);
	rg.accumTexture = R_LoadTexture("_accum", data2D, 16, 16, 3, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_LINEAR, TW_CLAMP);

	// Mirror render texture
	memset(data2D, 255, 16*16*4);
	rg.mirrorRenderTexture = R_LoadTexture("_mirrorRender", data2D, 16, 16, 3, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_LINEAR, TW_CLAMP);

	// Remote render texture
	memset(data2D, 255, 16*16*4);
	rg.remoteRenderTexture = R_LoadTexture("_remoteRender", data2D, 16, 16, 3, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_LINEAR, TW_REPEAT);

	// Current render texture
	memset(data2D, 255, 16*16*4);
	rg.currentRenderTexture = R_LoadTexture("_currentRender", data2D, 16, 16, 3, TF_INTERNAL | TF_NOPICMIP | TF_UNCOMPRESSED, TF_LINEAR, TW_CLAMP);
}


/*
 ==============================================================================

    CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 R_TakeScreenshot
 ==================
*/
static bool R_TakeScreenshot (const char *name)
{
	byte	*buffer;
	int		i, size;

	// Allocate the buffer
	size = 18 + (glConfig.videoWidth * glConfig.videoHeight * 3);
	buffer = (byte *)Mem_Alloc(size, rg.textureMemoryPool);

	// Set up the header
	memset(buffer, 0, 18);

	buffer[2] = TGA_IMAGE_TRUECOLOR;
	buffer[12] = glConfig.videoWidth & 255;
	buffer[13] = glConfig.videoWidth >> 8;
	buffer[14] = glConfig.videoHeight & 255;
	buffer[15] = glConfig.videoHeight >> 8;
	buffer[16] = 24;

	// Read the pixels
	qglReadBuffer(GL_FRONT);
	qglReadPixels(0, 0, glConfig.videoWidth, glConfig.videoHeight, GL_BGR, GL_UNSIGNED_BYTE, buffer + 18);

	// Apply gamma correction
	for (i = 18; i < size; i += 3)
	{
		buffer[i+0] = rg.gammaTable[buffer[i+0]];
		buffer[i+1] = rg.gammaTable[buffer[i+1]];
		buffer[i+2] = rg.gammaTable[buffer[i+2]];
	}

	// Write the image
	if (!FS_WriteFile2(name, buffer, size))
	{
		Mem_Free(buffer);
		return false;
	}

	Mem_Free(buffer);

	return true;
}

/*
 ==================
 R_Screenshot_f
 ==================
*/
void R_Screenshot_f ()
{
	char		name[MAX_PATH_LENGTH];
	char		map[MAX_PATH_LENGTH];
	time_t		t;
	struct tm	*lt;

	if (Cmd_Argc() > 2)
	{
		Com_Printf("Usage: screenshot [name]\n");
		return;
	}

	// Generate a file name
	if (Cmd_Argc() != 1)
	{
		Str_SPrintf(name, sizeof(name), "screenshots/%s", Cmd_Argv(1));
		Str_DefaultFileExtension(name, sizeof(name), ".tga");
	}
	else
	{
		time(&t);
		lt = localtime(&t);

		if (!rg.worldModel)
			Str_SPrintf(name, sizeof(name), "screenshots/shot_%02i%02i%02i_%02i%02i%02i.tga", lt->tm_mon + 1, lt->tm_mday, lt->tm_year % 100, lt->tm_hour, lt->tm_min, lt->tm_sec);
		else
		{
			Str_ExtractFileBase(rg.worldModel->name, map, sizeof(map));

			Str_SPrintf(name, sizeof(name), "screenshots/%s_%02i%02i%02i_%02i%02i%02i.tga", map, lt->tm_mon + 1, lt->tm_mday, lt->tm_year % 100, lt->tm_hour, lt->tm_min, lt->tm_sec);
		}
	}

	// Take the screenshot
	if (!R_TakeScreenshot(name))
	{
		Com_Printf("Couldn't write %s\n", name);
		return;
	}

	Com_Printf("Wrote %s\n", name);
}

// TODO!!!
void R_EnvShot_f ()
{
}

/*
 ==================
 R_ListTextures_f
 ==================
*/
void R_ListTextures_f ()
{
	texture_t	*texture;
	int			i;
	int			bytes = 0;

	Com_Printf("\n");
	Com_Printf("      -w-- -h-- -size- -fmt- type filt wrap -name--------\n");

	for (i = 0; i < r_numTextures; i++)
	{
		texture = r_textures[i];

		bytes += texture->uploadSize;

		Com_Printf("%4i: ", i);

		Com_Printf("%4i %4i ", texture->uploadWidth, texture->uploadHeight);

		Com_Printf("%5ik ", texture->uploadSize / 1024);

		switch (texture->uploadFormat){
		case GL_COMPRESSED_RGBA_ARB:
			Com_Printf("CRGBA ");
			break;
		case GL_COMPRESSED_RGB_ARB:
			Com_Printf("CRGB  ");
			break;
		case GL_COMPRESSED_LUMINANCE_ALPHA_ARB:
			Com_Printf("CLA   ");
			break;
		case GL_COMPRESSED_LUMINANCE_ARB:
			Com_Printf("CL    ");
			break;
		case GL_COMPRESSED_ALPHA_ARB:
			Com_Printf("CA    ");
			break;
		case GL_COMPRESSED_INTENSITY_ARB:
			Com_Printf("CI    ");
			break;
		case GL_RGBA8:
			Com_Printf("RGBA8 ");
			break;
		case GL_RGB8:
			Com_Printf("RGB8  ");
			break;
		case GL_LUMINANCE8_ALPHA8:
			Com_Printf("L8A8  ");
			break;
		case GL_LUMINANCE8:
			Com_Printf("L8    ");
			break;
		case GL_ALPHA8:
			Com_Printf("A8    ");
			break;
		case GL_INTENSITY8:
			Com_Printf("I8    ");
			break;
		default:
			Com_Printf("????? ");
			break;
		}

		switch (texture->uploadTarget){
		case GL_TEXTURE_2D:
			Com_Printf(" 2D  ");
			break;
		case GL_TEXTURE_CUBE_MAP_ARB:
			Com_Printf("CUBE ");
			break;
		default:
			Com_Printf("???? ");
			break;
		}

		switch (texture->filter){
		case TF_DEFAULT:
			Com_Printf("dflt ");
			break;
		case TF_NEAREST:
			Com_Printf("nrst ");
			break;
		case TF_LINEAR:
			Com_Printf("linr ");
			break;
		default:
			Com_Printf("???? ");
			break;
		}

		switch (texture->wrap){
		case TW_REPEAT:
			Com_Printf("rept ");
			break;
		case TW_CLAMP:
			Com_Printf("clmp ");
			break;
		case TW_CLAMP_TO_ZERO:
			Com_Printf("zero ");
			break;
		case TW_CLAMP_TO_ZERO_ALPHA:
			Com_Printf("azro ");
			break;
		default:
			Com_Printf("???? ");
			break;
		}

		Com_Printf("%s\n", texture->name);
	}

	Com_Printf("---------------------------------------------------------\n");
	Com_Printf("%i total textures\n", r_numTextures);
	Com_Printf("%.2f total megabytes of textures\n", bytes / 1048576.0);
	Com_Printf("\n");
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 R_InitTextures
 ==================
*/
void R_InitTextures ()
{
	int			i;
	int			r, g, b;
	float		f;
	byte		*palette;
	byte		defaultPalette[] = {
#include "palette.h"
	};

	// Build luminance table
	for (i = 0; i < 256; i++)
	{
		f = (float)i;

		r_luminanceTable[i][0] = f * 0.299;
		r_luminanceTable[i][1] = f * 0.587;
		r_luminanceTable[i][2] = f * 0.114;
	}

	// Load the palette
	if (!R_LoadPCX("pics/colormap.pcx", NULL, &palette, NULL, NULL, NULL))
		palette = defaultPalette;

	for (i = 0; i < 256; i++)
	{
		r = palette[i*3+0];
		g = palette[i*3+1];
		b = palette[i*3+2];

		r_palette[i] = (r << 0) + (g << 8) + (b << 16) + (255 << 24);
		r_palette[i] = LittleLong(r_palette[i]);
	}
	r_palette[255] &= LittleLong(0x00FFFFFF);	// 255 is transparent

	if (palette != defaultPalette)
		Mem_Free(palette);

	// Set texture parameters
	R_UpdateTextureParameters();

	// Create built-in textures
	R_CreateBuiltInTextures();
}

/*
 ==================
 R_ShutdownTextures
 ==================
*/
void R_ShutdownTextures ()
{
	texture_t	*texture;
	int			i;

	for (i = MAX_TEXTURE_UNITS - 1; i >= 0; i--)
	{
		if (i >= glConfig.maxTextureUnits)
			continue;

		GL_SelectTexture(i);

		qglBindTexture(GL_TEXTURE_2D, 0);
		qglBindTexture(GL_TEXTURE_CUBE_MAP_ARB, 0);
	}

	for (i = 0; i < r_numTextures; i++)
	{
		texture = r_textures[i];

		qglDeleteTextures(1, &texture->texNum);

		Mem_Free(texture);
	}

	memset(r_texturesHashTable, 0, sizeof(r_texturesHashTable));
	memset(r_textures, 0, sizeof(r_textures));

	r_numTextures = 0;
}